package auth

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"net/http"
	"ruoyi-go/internal/core/constants"
	"ruoyi-go/internal/framework/redisclient"
	"ruoyi-go/internal/framework/utils/rest"
	"ruoyi-go/internal/web/system/domain/vo"
	"strings"
	"time"
)

var whitelistPaths = []string{ // 白名单
	"/login",        // 登录
	"/captchaImage", // 验证码
	"/register",     // 注册
	"/logout",       // 退出
}

// JWTAuthMiddleware 创建一个 JWT 验证的中间件
func JWTAuthMiddleware() gin.HandlerFunc {
	// 将白名单路径转换为映射以便快速查找
	whitelist := make(map[string]bool)
	for _, path := range whitelistPaths {
		whitelist[path] = true
	}

	return func(c *gin.Context) {
		// 获取当前请求路径
		path := c.FullPath()
		// 如果请求路径不包含任何路由参数(如:id)，则使用请求的URL路径
		if path == "" {
			path = c.Request.URL.Path
		}

		// 检查路径是否在白名单中
		if whitelist[path] {
			// 路径在白名单中，跳过验证
			c.Next()
			return
		}

		// 部分路径匹配（例如检查前缀）
		for whitelistPath := range whitelist {
			// 如果白名单路径以 * 结尾，则视为前缀匹配
			if strings.HasSuffix(whitelistPath, "*") {
				prefix := strings.TrimSuffix(whitelistPath, "*")
				if strings.HasPrefix(path, prefix) {
					c.Next()
					return
				}
			}
		}

		// 从 Authorization 头获取令牌
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			rest.FailWithCode("Authorization header is required", http.StatusUnauthorized).Send(c)
			c.Abort()
			return
		}

		// 检查 Bearer 前缀
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			rest.FailWithCode("Authorization header format must be Bearer {token}", http.StatusUnauthorized).Send(c)
			c.Abort()
			return
		}

		// 获取令牌字符串
		tokenString := parts[1]

		// 验证令牌
		loginUser, claims, err := ValidateToken(tokenString)
		if err != nil {
			rest.FailWithCode(err.Error(), http.StatusUnauthorized).Send(c)
			c.Abort()
			return
		}

		// 将用户信息存储在上下文中
		c.Set("loginUser", loginUser)
		c.Set("uuid", claims.Subject)

		c.Next()
	}
}

// 用于签名的密钥
var jwtKey = []byte("your_secret_key")

// Claims 自定义的 claims
type Claims struct {
	UserID   int    `json:"userId"`
	Username string `json:"username"`
	jwt.RegisteredClaims
}

// GenerateTokenForLoginUser 创建 token。同时在redis记录用户信息
func GenerateTokenForLoginUser(user *vo.LoginUser) (string, error) {
	// uuid
	uuid := uuid.New().String() // 生成一个uuid，当作redis缓存的key
	user.Token = uuid
	// 创建 Claims
	claims := &Claims{
		UserID:   user.UserID,
		Username: user.User.UserName,
		RegisteredClaims: jwt.RegisteredClaims{
			// 在 JWT 中注册的声明
			// ExpiresAt: jwt.NewNumericDate(expirationTime),// 永不过期
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    "your-application-name",
			Subject:   uuid,                      // uuid放到token中
			Audience:  []string{"your-audience"}, // 可选的受众
		},
	}
	// 创建带有声明的令牌
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 使用密钥签名令牌并获取完整的编码令牌作为字符串
	tokenString, err := token.SignedString(jwtKey)
	if err != nil {
		return "", err
	}
	// 计算过期时间
	user.ExpireTime = time.Now().Add(time.Second * constants.TOKEN_EXPIRE_TIME)
	// 保存到redis中
	redisKey := fmt.Sprintf("%s%s", constants.LOGIN_TOKEN_KEY, uuid)
	redisclient.SetexObj(redisKey, user, constants.TOKEN_EXPIRE_TIME) // 缓存到redis中
	return tokenString, nil
}

// ValidateToken 解析和验证 JWT 令牌，如果不为空，则进行续费
func ValidateToken(tokenString string) (*vo.LoginUser, *Claims, error) {
	// 初始化自定义 Claims
	claims := &Claims{}

	// 解析令牌
	token, err := jwt.ParseWithClaims(
		tokenString,
		claims,
		func(token *jwt.Token) (interface{}, error) {
			// 验证签名算法
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return jwtKey, nil
		},
	)

	// 处理解析错误
	if err != nil {
		// 检查令牌是否过期
		if errors.Is(err, jwt.ErrTokenExpired) {
			return nil, nil, fmt.Errorf("token expired")
		} else if errors.Is(err, jwt.ErrTokenMalformed) {
			return nil, nil, fmt.Errorf("token format is invalid")
		} else if errors.Is(err, jwt.ErrTokenSignatureInvalid) {
			return nil, nil, fmt.Errorf("token signature is invalid")
		} else if errors.Is(err, jwt.ErrTokenNotValidYet) {
			return nil, nil, fmt.Errorf("token is not valid yet")
		}
		return nil, nil, err
	}

	// 验证令牌是否有效
	if !token.Valid {
		return nil, nil, fmt.Errorf("invalid token")
	}
	// 判断是否过期，从redis判断
	uuid, err := token.Claims.GetSubject()
	if err != nil {
		return nil, nil, err
	}
	redisKey := fmt.Sprintf("%s%s", constants.LOGIN_TOKEN_KEY, uuid)
	obj, err := redisclient.GetObj[vo.LoginUser](redisKey)
	if err != nil { // token过期
		return nil, nil, fmt.Errorf("token expired")
	}
	if time.Now().Sub(obj.ExpireTime) < 10*time.Minute { // 如果只剩10分钟，则重新续费
		// 重新计算过期时间
		obj.ExpireTime = time.Now().Add(time.Second * constants.TOKEN_EXPIRE_TIME)
		// 重新保存到redis中
		redisclient.SetexObj(redisKey, obj, constants.TOKEN_EXPIRE_TIME)
	}
	return obj, claims, nil
}

func GetLoginUser(c *gin.Context) *vo.LoginUser {
	loginUser, ok := c.Get("loginUser")
	if !ok {
		return nil
	}
	return loginUser.(*vo.LoginUser)
}

func DeleteLoginUser(c *gin.Context) {
	uuid, ok := c.Get("uuid")
	if !ok {
		return
	}
	redisKey := fmt.Sprintf("%s%s", constants.LOGIN_TOKEN_KEY, uuid)
	redisclient.DeleteObj(redisKey)
}

func RefreshToken(u *vo.LoginUser) {
	// 重新计算过期时间
	u.ExpireTime = time.Now().Add(time.Second * constants.TOKEN_EXPIRE_TIME)
	// 重新保存到redis中
	redisKey := fmt.Sprintf("%s%s", constants.LOGIN_TOKEN_KEY, u.Token)
	redisclient.SetexObj(redisKey, u, constants.TOKEN_EXPIRE_TIME)
}
